﻿using System;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.Security.Cryptography.Xml;
using System.Web;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Services;
using System.Net;
using System.IO;
using ServiceLibrary.Model;
using System.Configuration;

namespace ServiceLibrary.BLayer
{
    public class X509SecurityService
    {

        private static log4net.ILog log;
        private static bool writeDebug = Convert.ToBoolean(ConfigurationManager.AppSettings["DEBUG"]);

        #region Comando "makecert" per la generazione di un CA utile in fase di Firma durante lo sviluppo perchè ha una PrivateKey

        /*
         * Certificato Utilizzato in sede di sviluppo; qualora ci sia la necessità di firma dei documenti XML
         * sarebbe opportuno che il CSI ci fornisca il suo CA con relativa chiave PRIVATA.
         * 
         * L'attuale certificato da me creato è stato generato con il comando nativo:
         * 
         * La Private Key si trova in: C:\Windows\SysWOW64
         * Il CMD da usare è quello VisualStudio X64 aperto con privilegi di Amministratore
         * 
         * 
            1. Digitare il comando:
                makecert -pe -n "CN=DavideCA" -ss My -sr LocalMachine -sky signature -r "DavideCA.cer"
 
            2. Digitare il comando:
                makecert -pe -n "CN=http://CheckService.central.services.IntegraSISM.babele.csi.it" -ss my -sr LocalMachine -sky exchange -eku 1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2 -in "DavideCA" -is My -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 SPFTestCert.cer
         *          * 
         * 
         * 
         */
        private static string Certificate = "CN=DavideCA";

        #endregion

        #region Metodo da utilizzare soo in sede di SVILUPPO: Create / Sign / Verify

        public static void Run()
        {
            string virtualDir = HttpRuntime.AppDomainAppPath;

            //string xmlSample = virtualDir + @"ExternalSource\Gre.xml";
            //string xmlSampleSigned = virtualDir + @"ExternalSource\GreFirmato.xml";
            string xmlSample = virtualDir + @"ExternalSource\InviaRichiesta.xml";
            string xmlSampleSigned = virtualDir + @"ExternalSource\InviaRichiestaFirmato.xml";

            X509Certificate2 cert = GetCertificateBySubject(Certificate);

            bool isX509Sign = SignXmlFile(xmlSample, xmlSampleSigned, cert);
            bool isX509Check = VerifyXmlFile(xmlSampleSigned);
        }

        #endregion

        #region GetCertificateBySubject

        protected static X509Certificate2 GetCertificateBySubject(string CertificateSubject)
        {
            #region LOG

            if (writeDebug)
            {
                LOGHelp _logHelp = LOGHelp.Instance;
                _logHelp.LoadLog4NeFromCongif();
                log = log4net.LogManager.GetLogger("Servizio");
            }

            #endregion

            log.Debug("X509 START: #Trace: GetCertificateBySubject#");

            // Check the args.
            if (null == CertificateSubject)
                throw new ArgumentNullException("CertificateSubject");


            // Load the certificate from the certificate store.
            X509Certificate2 cert = null;

            /*
             *  Dove andremo a installare il CA sul Server? Più corretto dire: dove si troverà la CA sul server di CSI?
             *  In fase di sviluppo è stato creato il CN=DavideCA ed è stato posizionato sotto:
             *  -Certificati computer locale
             *  --Personale
             */
            X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

            try
            {
                // Open the store.
                store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

                // Get the certs from the store.
                X509Certificate2Collection CertCol = store.Certificates;

                // Find the certificate with the specified subject.
                foreach (X509Certificate2 c in CertCol)
                {
                    if (c.Subject.IndexOf(CertificateSubject) > -1)
                    {
                        cert = c;
                        break;
                    }
                }

                // Throw an exception of the certificate was not found.
                if (cert == null)
                {
                    log.Error("X509 Error: The certificate could not be found");
                    throw new Exception("The certificate could not be found.");
                }
            }
            finally
            {
                // Close the store even if an exception was thrown.
                store.Close();
            }

            log.Debug("X509 STOP: #Trace: GetCertificateBySubject#");

            return cert;
        }

        #endregion

        #region VerifyXmlFile

        public static bool VerifyXmlFile(string SignedFileName)
        {
            bool isX509Check = false;

            #region LOG

            if (writeDebug)
            {
                LOGHelp _logHelp = LOGHelp.Instance;
                _logHelp.LoadLog4NeFromCongif();
                log = log4net.LogManager.GetLogger("Servizio");
            }

            #endregion

            log.Debug("X509 START: #Trace: VerifyXmlFile#");

            try
            {
                XmlDocument xmlDocument = new XmlDocument();

                xmlDocument.PreserveWhitespace = true;

                xmlDocument.Load(SignedFileName);

                XmlNodeList nodeList = xmlDocument.GetElementsByTagName(
                    "Signature", SignedXml.XmlDsigNamespaceUrl);

                for (int curSignature = 0; curSignature < nodeList.Count; curSignature++)
                {
                    BabeleSignedXml signedXml = new BabeleSignedXml(xmlDocument);

                    signedXml.LoadXml((XmlElement)nodeList[curSignature]);

                    //Read SecurityTokenReference List
                    XmlNodeList referenceList = signedXml.KeyInfo.GetXml().GetElementsByTagName(
                                                                                        "Reference",
                                                                                        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");

                    if (referenceList.Count == 0)
                    {
                        log.Error("X509 Eror: No KeyInfo found. Please contact the system administrator.");
                        throw new XmlException("No KeyInfo found. Please contact the system administrator.");
                    }

                    //Read all BinarySecurityToken
                    string binaryTokenReference = null;
                    foreach (XmlAttribute attribute in referenceList[0].Attributes)
                    {
                        if (attribute.Name.ToUpper().Equals("URI"))
                        {
                            binaryTokenReference = attribute.Value.Substring(1);
                            break;
                        }
                    }

                    if (string.IsNullOrEmpty(binaryTokenReference))
                    {
                        log.Error("X509 Error: No SecurityTokenReference found. Please contact the system administrator.");
                        throw new XmlException("No SecurityTokenReference found. Please contact the system administrator.");
                    }

                    XmlElement binaryTokenElement = signedXml.GetIdElement(xmlDocument, binaryTokenReference);

                    if (binaryTokenElement == null)
                    {
                        log.Error("X509 Error: No BinarySecurityToken found. Please contact the system administrator.");
                        throw new XmlException("No BinarySecurityToken found. Please contact the system administrator.");
                    }

                    //Extract Certificate from SOAP
                    byte[] certBytes = Convert.FromBase64String(binaryTokenElement.InnerText);
                    X509Certificate2 cert = new X509Certificate2();
                    cert.Import(certBytes);

                    //Check Signature
                    bool result = signedXml.CheckSignature(cert.PublicKey.Key);

                    if (result)
                        isX509Check = true;
                    else
                        isX509Check = false;
                }
            }
            catch (Exception ex)
            {
                isX509Check = false;
                log.Error("X509 Certification error: " + ((ex.InnerException != null) ? ex.InnerException.ToString() : ex.Message.ToString()));
                throw new Exception("X509 Certification error: " + ((ex.InnerException != null) ? ex.InnerException.ToString() : ex.Message.ToString()));
            }

            log.Debug("X509 STOP: #Trace: VerifyXmlFile#");

            return isX509Check;
        }

        #endregion

        #region VerifySOAPMessage

        public static bool VerifySOAPMessage(XmlDocument SoapMessage)
        {
            bool isX509Check = false;

            #region LOG

            if (writeDebug)
            {
                LOGHelp _logHelp = LOGHelp.Instance;
                _logHelp.LoadLog4NeFromCongif();
                log = log4net.LogManager.GetLogger("Servizio");
            }

            #endregion

            log.Debug("X509 START: #Trace: VerifySOAPMessage#");

            try
            {
                XmlDocument xmlDocument = new XmlDocument();

                xmlDocument.PreserveWhitespace = true;

                xmlDocument = SoapMessage;

                XmlNodeList nodeList = xmlDocument.GetElementsByTagName(
                    "Signature", SignedXml.XmlDsigNamespaceUrl);

                for (int curSignature = 0; curSignature < nodeList.Count; curSignature++)
                {
                    BabeleSignedXml signedXml = new BabeleSignedXml(xmlDocument);

                    signedXml.LoadXml((XmlElement)nodeList[curSignature]);

                    //Read SecurityTokenReference List
                    XmlNodeList referenceList = signedXml.KeyInfo.GetXml().GetElementsByTagName(
                                                                                        "Reference",
                                                                                        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");

                    if (referenceList.Count == 0)
                    {
                        log.Error("X509 Error: No KeyInfo found. Please contact the system administrator.");
                        throw new XmlException("No KeyInfo found. Please contact the system administrator.");
                    }

                    //Read all BinarySecurityToken
                    string binaryTokenReference = null;
                    foreach (XmlAttribute attribute in referenceList[0].Attributes)
                    {
                        if (attribute.Name.ToUpper().Equals("URI"))
                        {
                            binaryTokenReference = attribute.Value.Substring(1);
                            break;
                        }
                    }

                    if (string.IsNullOrEmpty(binaryTokenReference))
                    {
                        log.Error("X509 Error: No SecurityTokenReference found. Please contact the system administrator.");
                        throw new XmlException("No SecurityTokenReference found. Please contact the system administrator.");
                    }

                    XmlElement binaryTokenElement = signedXml.GetIdElement(xmlDocument, binaryTokenReference);

                    if (binaryTokenElement == null)
                    {
                        log.Error("X509 Error: No BinarySecurityToken found. Please contact the system administrator.");
                        throw new XmlException("No BinarySecurityToken found. Please contact the system administrator.");
                    }

                    //Extract Certificate from SOAP
                    byte[] certBytes = Convert.FromBase64String(binaryTokenElement.InnerText);
                    X509Certificate2 cert = new X509Certificate2();
                    cert.Import(certBytes);

                    //Check Signature
                    bool result = signedXml.CheckSignature(cert.PublicKey.Key);

                    if (result)
                        isX509Check = true;
                    else
                        isX509Check = false;
                }
            }
            catch (Exception ex)
            {
                isX509Check = false;
                log.Error("X509 Certification error: " + ((ex.InnerException != null) ? ex.InnerException.ToString() : ex.Message.ToString()));
                throw new Exception("X509 Certification error: " + ((ex.InnerException != null) ? ex.InnerException.ToString() : ex.Message.ToString()));
            }

            log.Debug("X509 STOP: #Trace: VerifySOAPMessage#");

            return isX509Check;
        }

        #endregion

        #region SignXmlFile

        static bool SignXmlFile(string FileName, string SignedFileName, X509Certificate2 Certificate)
        {
            bool isSignedX509 = false;

            #region LOG

            if (writeDebug)
            {
                LOGHelp _logHelp = LOGHelp.Instance;
                _logHelp.LoadLog4NeFromCongif();
                log = log4net.LogManager.GetLogger("Servizio");
            }

            #endregion

            log.Debug("X509 START: #Trace: SignXmlFile#");

            try
            {
                System.Xml.XmlDocument doc = new XmlDocument();
                doc.PreserveWhitespace = false;
                doc.Load(new XmlTextReader(FileName));

                BabeleSignedXml signedXml = new BabeleSignedXml(doc);

                signedXml.SigningKey = Certificate.PrivateKey;  //------> se da Keyset non trovato è un problema di permessi all'utente
                //---------->da MMC selezionare con il tasto destro il certificato
                //------>Tutte le attività + Gestisci chiavi private + permessi a IIS_IUSRS
                Reference reference = new Reference();

                /*
                 
                 * In sede di Validazione il "reference.Uri" viene estratto dalla sezione crittografata al fine di 
                 * evitare manomissioni. Se in sede di DEBUG si modifica in qualche modo l'XML la validazione sarà
                 * SEMPRE non validata.
                 
                 */
                reference.Uri = "#Body-556EAC0E-C189-4984-8A5E-F1CD2CC4061A";
                reference.DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1";

                XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
                reference.AddTransform(env);
                XmlDsigExcC14NTransform c14 = new XmlDsigExcC14NTransform();
                reference.AddTransform(c14);
                signedXml.AddReference(reference);
                KeyInfo keyInfo = new KeyInfo();
                keyInfo.AddClause(new KeyInfoX509Data(Certificate));
                signedXml.KeyInfo = keyInfo;
                signedXml.SignedInfo.CanonicalizationMethod = c14.Algorithm;
                signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

                signedXml.ComputeSignature();

                XmlElement xmlDigitalSignature = signedXml.GetXml();
                doc.GetElementsByTagName("Signature")[0].PrependChild(doc.ImportNode(xmlDigitalSignature.GetElementsByTagName("SignatureValue")[0], true));
                doc.GetElementsByTagName("Signature")[0].PrependChild(doc.ImportNode(xmlDigitalSignature.GetElementsByTagName("SignedInfo")[0], true));
                doc.GetElementsByTagName("BinarySecurityToken")[0].InnerText = xmlDigitalSignature.GetElementsByTagName("X509Certificate")[0].InnerText;

                using (XmlTextWriter xmltw = new XmlTextWriter(SignedFileName,
                    new UTF8Encoding(false)))
                {
                    doc.WriteTo(xmltw);
                }

                isSignedX509 = true;
            }
            catch (Exception ex)
            {
                isSignedX509 = false;
                log.Error("X509 Error: " + ((ex.InnerException != null) ? ex.InnerException.ToString() : ex.Message.ToString()));
                throw new Exception("X509 Sign error: " + ((ex.InnerException != null) ? ex.InnerException.ToString() : ex.Message.ToString()));
            }

            log.Debug("X509 STOP: #Trace: SignXmlFile#");

            return isSignedX509;
        }

        #endregion

        #region class BabeleSignedXml : SignedXml

        class BabeleSignedXml : SignedXml
        {
            public BabeleSignedXml(XmlDocument document)
                : base(document)
            {
            }
            public override XmlElement GetIdElement(XmlDocument document, string idValue)
            {
                XmlNameTable myXmlNameTable = new NameTable();
                XmlNamespaceManager myNamespacemanager = new XmlNamespaceManager(myXmlNameTable);
                myNamespacemanager.AddNamespace("wsu",
                        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
                XmlNodeList lst = document.SelectNodes("//*[@wsu:Id='" + idValue + "' or @wsu:ID='" + idValue +
                        "' or @wsu:ID='" + idValue + "']", myNamespacemanager);
                if (lst.Count != 1)
                    return null;
                return (XmlElement)lst.Item(0);
            }
        }

        #endregion

        #region EnevelopeDefinition

        public static string EnevelopeDefinition(string xmlType, string types)
        {
            string soap = @"<?xml version=""1.0"" encoding=""utf-8""?>";
            soap += @"<soapenv:Envelope";
            soap += @" xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/""";
            soap += @" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""";
            soap += @" xmlns:s=""http://www.w3.org/2001/XMLSchema""";
            soap += @" xmlns:sgt=""http://CheckService.central.services.IntegraSISM.babele.csi.it"">";
            soap += @"<soapenv:Header />";
            soap += @"<soapenv:Body>";
            soap += @"<sgt:" + types + ">";

            //TODO: Inserire il contenuto xml estratto
            soap += xmlType;

            soap += @"</sgt:" + types + ">";
            soap += @"</soapenv:Body>";
            soap += @"</soapenv:Envelope>";

            return soap;
        }

        #endregion

    }
}